#!/usr/bin/env python
from __future__ import with_statement

import re, time

from myqq import *

class Client(object):
    DEFAULT_ENCODING = 'GBK'
    
    def __init__(self, uid, passwd, encoding=None):
        self.qq = qqclient()
        self.uid = uid
        self.passwd = passwd
        self.encoding = encoding or self.guessEncoding()
        
        qqclient_create(self.qq, int(uid), passwd)

    def __del__(self):
        qqclient_cleanup(self.qq)
        del self.qq
        self.qq = None
        
    def __enter__(self):
        return self
    
    def __exit__(self, exc_type, exc_value, traceback):
        self.logout()
        del self
        
    @staticmethod
    def init():
        config_init()
        qqsocket_init()
        
    @staticmethod
    def done():
        config_end()
        
    @staticmethod
    def guessEncoding():
        from locale import getdefaultlocale
        
        lang, encoding = getdefaultlocale()
        
        return encoding or Client.DEFAULT_ENCODING
    
    def decode(self, value, encoding=None):
        return value.decode('utf-8').encode(encoding or self.encoding)
        
    def encode(self, value, encoding=None):
        if type(value) != 'unicode':
            value = value.decode(encoding or self.encoding)
                    
        return value.encode('utf-8')        
        
    def setHttpProxy(host_or_ip, port):
        from socket import gethostbyname, inet_aton
        from struct import unpack
        
        self.qq.network = PROXY_HTTP
        self.qq.proxy_server_ip = unpack('I', inet_aton(gethostbyname(host_or_ip)))[0]
        self.qq.proxy_server_port = port
        
    def login(self, mode=QQ_ONLINE):
        self.qq.mode = mode
        
        qqclient_login(self.qq)
        
        while self.qq.process != P_LOGIN:
            qqclient_wait(self.qq, 1)
        
            if self.qq.process in [P_LOGGING, P_VERIFYING, P_WRONGPASS]:
                yield self.qq.process
            else:
                yield self.qq.process
                break
        
    def logout(self):
        qqclient_logout(self.qq)
        
    @property
    def logined(self):
        return self.qq.process == P_LOGIN
    
    def __nonzero__(self):
        return self.qq.process != P_INIT
        
    def verify(self, captcha):
        from struct import unpack
        
        qqclient_verify(self.qq, unpack('I', captcha))
        
    def update(self):
        qun_update_all(self.qq);
        buddy_update_list(self.qq);
        group_update_list(self.qq);
        
    @property
    def self(self):
        return Buddy(self, self.qq.self)
                
    def getStatus(self):
        return int(qq.self.status)
        
    def setStatus(self, status):
        try:
            qqclient_change_status(qq, int(status))
        except TypeError:
            for key, value in QQ_STATUS_NAMES.items():
                if value == status:
                    self.status = key
    
    status = property(getStatus, setStatus)
    
    RE_MSG = re.compile(r"^buddymsg\^\$(?P<uid>\d+)\^\$(?P<time>[\d\s\-:]+)\^\$(?P<msg>.*)$")
    
    MSG_BUDDY_MESSAGE = 'buddymsg'
    MSG_BUDDY_LIST = 'buddylist'
    MSG_OTHER = 'other'
    
    @property
    def messages(self, size=1024):
        while self.logined:
            msg = get_message(self.qq, size, 1)
            
            if msg is None:
                
                continue
                
            msg = self.decode(msg)
                
            m = self.RE_MSG.match(msg)
            
            if m:
                yield (self.MSG_BUDDY_MESSAGE, { 'uid': int(m.group('uid')),
                                                 'time': m.group('time'),
                                                 'msg': m.group('msg') })
                continue
            
            yield (self.MSG_OTHER, { 'msg': msg })
       
    @property 
    def buddies(self):
        result = []
        
        list_lock(self.qq.buddy_list)
        try:
            for i in range(self.qq.buddy_list.count):
                result.append(Buddy(self, get_buddy_item(self.qq.buddy_list, i)))
        finally:
            list_unlock(self.qq.buddy_list)
            
        return result
                        
    def findBuddies(self, uid=None, name=None, regex=None):
        for buddy in self.buddies:
            if (uid and buddy.uid == int(uid)) or \
               (name and buddy.name == name) or \
               (regex and regex.match(buddy.name)):
                yield buddy
                
    def findBuddy(self, uid=None, name=None):
        buddies = [buddy for buddy in self.findBuddies(uid, name)]
        
        return buddies[0] if buddies else None
            
    @property
    def groups(self):
        result = []
        
        list_lock(self.qq.group_list)
        try:
            for i in range(self.qq.group_list.count):
                result.append(Group(self, get_group_item(self.qq.group_list, i)))
        finally:
            list_unlock(self.qq.group_list)
            
        return result
    
    def findGroups(self, name=None, regex=None):
        for group in self.groups:
            if (name and group.name == name) or \
               (regex and regex.match(group.name)):
                yield group
    
    def findGroup(self, name):
        groups = [gropu for group in self.findGroups(name)]
        
        return groups[0] if groups else None
    
    @property
    def quns(self):
        result = []
        
        list_lock(self.qq.qun_list)
        try:
            for i in range(self.qq.qun_list.count):
                result.append(Qun(self, get_qun_item(self.qq.qun_list, i)))
        finally:
            list_unlock(self.qq.qun_list)
            
        return result
            
    def findQuns(self, num=None, ext=None, owner=None, name=None, regex=None):
        for qun in self.quns:
            if (num and qun.number == int(num)) or \
               (ext and qun.ext == int(ext)) or \
               (owner and qun.owner == int(owner)) or \
               (name and qun.nickname == name) or \
               (regex and regex.match(qun.nickname)):
                yield qun

Client.init()

class Buddy(object):
    def __init__(self, client, buddy):
        self.client = client
        self.buddy = buddy
        
    @property
    def uid(self):
        return self.buddy.number
    
    @property
    def name(self):
        return self.client.decode(self.buddy.alias or self.buddy.nickname)
    
    @property
    def status(self):
        return self.buddy.status
    
    @property
    def info(self):
        return self.buddy
    
    @property
    def ip(self):
        from socket import inet_ntoa
        from struct import pack
        
        return inet_ntoa(pack('I', self.buddy.ip))
        
    @staticmethod
    def invite(self, uid, msg='', encoding=None):
        qqclient_add(self.client.qq, self.uid, self.client.encode(msg))
        
    def delete(self):
        qqclient_del(self.client.qq, self.uid)
        
    def say(self, msg, encoding=None, timeout=15):
        buddy_send_message(self.client.qq, self.uid, self.client.encode(msg))
                            
        return (qqclient_wait(self.client.qq, timeout) >= 0) if timeout else True
        
    def update(self, timeout=15):
        buddy_update_info(self.client.qq, self.buddy)
        
        if timeout and qqclient_wait(self.client.qq, timeout) < 0:
            return None
            
        return self.buddy

class Group(object):
    def __init__(self, client, group):
        self.client = client
        self.group = group
    
    @property
    def id(self):
        return self.group.number
         
    @property   
    def name(self):
        return self.client.decode(self.group.name)
    
class Member(object):
    def __init__(self, qun, member):
        self.qun = qun
        self.member = member
    
class Qun(object):
    def __init__(self, client, qun):
        self.client = client
        self.qun = qun
        
    @property
    def number(self):
        return self.qun.number
    
    @property
    def ext(self):
        return self.qun.ext_number
    
    @property
    def name(self):
        return self.client.decode(self.qun.name)
    
    @property
    def members(self):
        list_lock(self.qun.member_list)
        try:
            for i in range(self.qun.member_list.count):
                yield Member(self, qun_member_get(self.qun.client.qq, self.qun, self.qun.member_list, i, 0))
        finally:
            list_unlock(self.qun.member_list)
            
    @property
    def count(self):
        return self.qun.member_list.count

import threading
import logging

def parse_cmdline():
    from optparse import OptionParser
    
    parser = OptionParser()
    
    parser.add_option("-u", "--uid")
    parser.add_option("-p", "--passwd")
    parser.add_option("--encoding")

    parser.add_option("-q", "--quiet", action="store_const",
                      const=logging.FATAL, dest="logLevel", default=logging.WARN)
    parser.add_option("-v", "--verbose", action="store_const",
                      const=logging.INFO, dest="logLevel")
    parser.add_option("-d", "--debug", action="store_const",
                      const=logging.DEBUG, dest="logLevel")
    parser.add_option("--log-format", dest="logFormat",
                      default="%(asctime)s %(levelname)s %(message)s")
    
    opts, args = parser.parse_args()
    
    return opts, args
        
def login(client):    
    print "loging...",
    
    for p in client.login():
        if p == P_LOGIN:
            print "succeeded."
        elif p == P_LOGGING:
            print ".",
        elif p == P_VERIFYING:
            print 
            
            while True:
                captcha = raw_input("please input the captcha in verify folder: ")
            
                if len(captcha) == 4: break
                
                print "ERROR: captcha should be 4 chars"                    
            
            client.verify(captcha)
        elif p == P_ERROR:
            print 
            print "ERROR: network failed."            
        elif p == P_DENIED:
            print
            print "WARN: your QQ need be activated. (http://jihuo.qq.com)"            
        elif p == P_WRONGPASS:
            print
            print "WARN: wrong password."
        else:
            print
            print "ERROR: unknown error, %s" % qq.process

def show_msg(client):
    for type, args in client.messages:
        try:
            if type == Client.MSG_BUDDY_MESSAGE:
                print "QQ#%(uid)d say (%(time)s): %(msg)s" % args
            else:
                print args['msg']
                
            time.sleep(5)
        except Exception, e:
            print "ERROR: fail to show message"
            print e
                
if __name__ == '__main__':
    opts, args = parse_cmdline()
    
    logging.basicConfig(level=opts.logLevel,
                        format=opts.logFormat)    
        
    qq_uid = opts.uid or raw_input("QQ#  : ")
    qq_passwd = opts.passwd or raw_input("pass : ")
    
    with Client(qq_uid, qq_passwd, opts.encoding) as client:
        login(client)
        
        t = threading.Thread(target=show_msg, args=(client,), name="show_msg")        
        t.setDaemon(True)
        t.start()
                
        while client:
            line = raw_input(">").strip()
            
            if not line: continue
            
            cmd = line.split()[0]
            params = line[len(cmd)+1:].split()
            
            if cmd in ['exit', 'quit', 'e', 'q']:
                break
            
            elif cmd in ['login']:                
                try:
                    client.logout()
                    client = Client(params[0], params[1], opts.encoding)
                    login(client)                    
                except (IndexError, TypeError):
                    print "login <QQ#> <password>"
            
            elif client is None:
                print "WARN: please login QQ first"
                
            elif cmd in ['logout']:
                client.logout()
                del client
                client = None
            
            elif cmd in ['add', 'a']:
                try:
                    Buddy.invite(params[0], ''.join(params[1:]))
                except (IndexError, TypeError):
                    print "add <QQ#> [message]"
                
            elif cmd in ['del', 'delete', 'd']:
                try:
                    for param in params:
                        for buddy in client.findBuddies(uid=param):
                            buddy.delete()
                except (IndexError, TypeError), e:
                    print "del <QQ#...>"
                    print e
                    
            elif cmd in ['say', 's']:
                try:
                    if not client.findBuddy(params[0]).say(''.join(params[1:])):
                        print "WARN: fail to send message"
                except (IndexError, TypeError, AttributeError), e:
                    print "say <QQ#> <message>"
                    print e
                
            elif cmd in ['status']:
                if params:
                    client.status = params[0]
                else:
                    print "QQ# %d <%s> is %s" % (client.uid, client.self.name,
                                                 QQ_STATUS_NAMES[client.status])
            elif cmd in ['update', 'u']:
                client.update()
                
            elif cmd in ['view', 'v']:
                if params:
                    pass
                else:
                    for buddy in client.buddies:
                        print "QQ# %d <%s> is %s" % (buddy.uid,
                                                     buddy.name,
                                                     QQ_STATUS_NAMES[buddy.status])
            elif cmd in ['group', 'g']:
                if params:
                    pass
                else:
                    for group in client.groups:
                        print "Group# %d <%s>" % (group.id, group.name)
                        
            elif cmd in ['qun']:
                if params:
                    pass
                else:
                    for qun in client.quns:
                        print "Qun# %d.%d <%s> w/ %d members" % (qun.num,
                                                                 qun.ext,
                                                                 qun.name,
                                                                 qun.member_list.count)
            elif cmd in ['info', 'i']:
                for uid in params:
                    buddy = client.findBuddy(uid).update()
                    
                    if buddy is None:
                        print "WARN: fail to query #%d's information" % uid
                        continue
                    
                    print "Nickname     : %s" % buddy.nickname
                    print "UID          : %d" % buddy.number
                    print "Signature    : %s" % buddy.signature
                    print "Alias        : %s" % buddy.alias
                    print "Mobile       : %s" % buddy.mobilephone
                    print "Email        : %s" % buddy.email
                    print "Occupation   : %s" % buddy.occupation
                    print "Homepage     : %s" % buddy.homepage
                    print "School       : %s" % buddy.school
                    print "Country      : %s" % buddy.country
                    print "Province     : %s" % buddy.province
                    print "City         : %s" % buddy.city
                    print "Address      : %s" % buddy.address
                    print "Brief        : %s" % buddy.brief
                    print "Face         : %d" % buddy.face
                    print "Age          : %d" % buddy.age
                    print "Sex          : %s" % ("Male" if buddy.sex == 0 else "Female" if buddy.sex == 1 else "Asexual")
                    print "Status       : %s" % QQ_STATUS_NAMES[int(buddy.status)] 